Skip to content

Add OAuth-protected MCP support for standalone Hyperforge#61

Open
arnaugrispg wants to merge 12 commits into
mainfrom
arnaugris/sc-15081/oauth-on-hyperforge-standalone
Open

Add OAuth-protected MCP support for standalone Hyperforge#61
arnaugrispg wants to merge 12 commits into
mainfrom
arnaugris/sc-15081/oauth-on-hyperforge-standalone

Conversation

@arnaugrispg

Copy link
Copy Markdown
Contributor

Description

Adds optional OAuth configuration for standalone Hyperforge MCP endpoints.

When configured, Hyperforge now:

  • Exposes OAuth protected resource metadata for MCP clients.
  • Returns 401 with the proper resource metadata reference when MCP requests are missing or have invalid bearer tokens.
  • Validates incoming JWT bearer tokens using the configured JWKS URL before allowing MCP traffic through.
  • Requires token expiration (exp) and rejects tokens when JWKS validation fails.
  • Optionally forwards the validated Authorization header to the inner MCP/service backend, enabling use cases like MarkLogic OAuth passthrough.

Includes focused standalone MCP auth tests covering metadata exposure, unauthorized requests, valid token passthrough, missing exp, and JWKS failure handling.

@arnaugrispg arnaugrispg requested a review from a team July 9, 2026 12:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds optional OAuth/JWT-based protection for standalone Hyperforge MCP endpoints, including protected-resource metadata discovery and bearer token validation against a configured JWKS, with tests validating expected 401/200 behaviors and optional Authorization passthrough.

Changes:

  • Introduces standalone OAuth/JWT validation (JWKS fetch/cache, JWT signature + claims + scope checks) and wires it into standalone auth middleware for MCP routes.
  • Adds per-agent mcp_auth configuration and uses it to emit RFC9728 protected-resource metadata and control Authorization header forwarding.
  • Adds standalone test coverage for protected vs open MCP behavior, metadata exposure, JWT failures, and Authorization passthrough control.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
hyperforge/tests/standalone/test_mcp_auth.py Adds standalone MCP OAuth/authn test suite (metadata, 401s, JWT validation, header passthrough).
hyperforge/src/hyperforge/standalone/oauth.py Implements JWKS caching and JWT bearer validation (signature/claims/scopes).
hyperforge/src/hyperforge/standalone/config.py Adds StandaloneMCPAuthConfig and mcp_auth per-agent configuration.
hyperforge/src/hyperforge/standalone/app.py Replaces open auth backend with standalone backend that enforces MCP OAuth when configured + emits proper 401 metadata.
hyperforge/src/hyperforge/api/v1/mcp_interaction.py Adds protected-resource metadata endpoint config override and optional Authorization forwarding control into interaction headers.
hyperforge/src/hyperforge/api/v1/interaction.py Updates ensure_session_exists to return effective session id + error tuple; updates websocket call site.
hyperforge/src/hyperforge/api/session.py Adds UUID pre-check to session_exists before calling NucliaDB by id.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hyperforge/src/hyperforge/standalone/oauth.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread hyperforge/src/hyperforge/api/v1/mcp_interaction.py
Comment thread hyperforge/src/hyperforge/standalone/oauth.py
Comment thread hyperforge/src/hyperforge/api/v1/interaction.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread hyperforge/src/hyperforge/standalone/oauth.py
Comment thread hyperforge/src/hyperforge/standalone/config.py Outdated
Comment thread hyperforge/src/hyperforge/api/v1/mcp_interaction.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread hyperforge/src/hyperforge/standalone/app.py
Comment thread hyperforge/tests/standalone/test_mcp_auth.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread hyperforge/src/hyperforge/api/session.py Outdated
Comment thread hyperforge/tests/standalone/test_mcp_auth.py Outdated
arnaugrispg and others added 2 commits July 9, 2026 16:55
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread hyperforge/src/hyperforge/standalone/oauth.py
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@carlesonielfa carlesonielfa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 comment

Comment on lines +204 to +228
auth_config = get_enabled_mcp_auth(getattr(app, "_agents_cfg", {}), agent_id)
resource = (
auth_config.protected_resource
if auth_config is not None and auth_config.protected_resource is not None
else str(mcp_url.replace(scheme="https"))
)
fallback_authorization_server = getattr(app.settings, "hydra_public_url", None)
authorization_servers = (
[auth_config.authorization_server]
if auth_config is not None and auth_config.authorization_server is not None
else (
[fallback_authorization_server]
if fallback_authorization_server is not None
else []
)
)
scopes_supported = (
auth_config.scopes_supported
if auth_config is not None
else getattr(app.settings, "hydra_scopes_supported", [])
)
return {
"resource": mcp_url_https,
"scopes_supported": app.settings.hydra_scopes_supported,
"authorization_servers": [app.settings.hydra_public_url],
"resource": resource,
"scopes_supported": scopes_supported,
"authorization_servers": authorization_servers,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to be using getattr instead of accessing the fields normally? getattr bypasses type checking and might be a source for errors

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used getattr to avoid conflicts between standalone and non-standalone settings, but I agree it’s better not to use it here, so I replaced it with a dedicated helper and removed the getattr usage.

missing = [
field
for field in ("authorization_server", "jwks_url")
if getattr(self, field) is None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another getattr here

app: "HTTPApplication", agent_id: str, headers: Headers
) -> dict[str, str]:
interaction_headers = dict(headers.items())
auth_config = get_enabled_mcp_auth(getattr(app, "_agents_cfg", {}), agent_id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a getattr to check it ? could be less hacky ?

auth_config = get_enabled_mcp_auth(getattr(app, "_agents_cfg", {}), agent_id)

authorization = headers.get("authorization")
if auth_config is not None and not auth_config.forward_authorization_header:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which use case covers this condition?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition was added to support using the token only for MCP auth, but this MCP will never have auth on its own, so I’m removing this use case.

if auth_config is not None and auth_config.protected_resource is not None
else str(mcp_url.replace(scheme="https"))
)
fallback_authorization_server = getattr(app.settings, "hydra_public_url", None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No getattr

scopes_supported = (
auth_config.scopes_supported
if auth_config is not None
else getattr(app.settings, "hydra_scopes_supported", [])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No getattr

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants